<template>
  <movable-area :style="[getAreaStyle]">
    <movable-view
      v-for="(item, index) in list"
      :animation="animation"
      :direction="direction"
      :key="item.key"
      :damping="damping"
      :x="item.x"
      :y="item.y"
      :disabled="longpress ? disabled : false"
      @longpress="handleLongpress"
      @touchstart="handleDragStart(index)"
      @change="handleMoving"
      @touchend="handleDragEnd"
      :style="[getViewStyle]"
      class="base-drag-wrapper"
      :class="{ active: activeIndex === index }"
    >
      <view
        :class="[
          'drag-item',
          'flex',
          'flex-column',
          'items-center',
          'justify-center',
          currentId == item.id ? 'active' : '',
        ]"
        @click="handleSwitch(item, index)"
      >
        <view class="image-wrapper">
          <image
            :src="item.image_url"
            style="width: 50px; height: 50px"
          ></image>
        </view>
        <view class="name">
          {{ item[itemKey] }}
        </view>
      </view>
    </movable-view>
  </movable-area>
</template>

<script>
export default {
  props: {
    currentId: {
      type: [Number, String],
      default: "",
    },
    column: {
      type: Number,
      default: 3,
    },
    value: {
      type: Array,
      default: () => [],
    },
    width: {
      type: String,
      default: "100%",
    },
    height: {
      type: String,
      default: "auto",
    },
    itemKey: {
      type: String,
      required: true,
    },
    itemHeight: {
      type: String,
      default: "100px",
    },
    direction: {
      type: String,
      default: "all",
      validator: (value) => {
        return ["all", "vertical", "horizontal", "none"].includes(value);
      },
    },
    animation: {
      type: Boolean,
      default: true,
    },
    damping: {
      type: Number,
      default: 20,
    },
    longpress: {
      type: Boolean,
      default: true,
    },
  },
  data() {
    return {
      list: [],
      disabled: true,
      activeIndex: -1,
      moveToIndex: -1,
      oldIndex: -1,
      tempDragInfo: {
        x: "",
        y: "",
      },
      cloneList: [],
    };
  },
  computed: {
    getAreaStyle() {
      const width = this.getRealWidth(this.width);
      return {
        width: width + "px",
        height:
          this.height !== "auto"
            ? this.height
            : Math.ceil(this.list.length / this.column) * this.getItemHeight +
              "px",
      };
    },
    getViewStyle() {
      const { itemHeight, getItemWidth } = this;
      return { width: getItemWidth + "px", height: itemHeight };
    },
    getItemHeight() {
      return parseFloat(this.itemHeight);
    },
    getItemWidth() {
      if (this.column === 0) return;
      const width = this.getRealWidth(this.width);
      return (parseFloat(width) / this.column).toFixed(2);
    },
  },
  methods: {
    //获取实际的宽度
    getRealWidth(width) {
      if (width.includes("%")) {
        const windowWidth = uni.getSystemInfoSync().windowWidth;
        width = windowWidth * (parseFloat(width) / 100);
      }
      return width;
    },

    initList(list = []) {
      const newList = this.deepCopy(list);
      this.list = newList.map((item, index) => {
        const [x, y] = this.getPosition(index);
        return {
          ...item,
          x,
          y,
          key: Math.random() + index,
        };
      });
      this.cloneList = this.deepCopy(this.list);
    },

    //长按
    handleLongpress() {
      this.disabled = false;
    },

    //拖拽开始
    handleDragStart(index) {
      this.activeIndex = index;
      this.oldIndex = index;
    },

    //拖拽中
    handleMoving(e) {
      if (e.detail.source !== "touch") return;
      const { x, y } = e.detail;
      Object.assign(this.tempDragInfo, { x, y });
      const currentX = Math.floor(
        (x + this.getItemWidth / 2) / this.getItemWidth
      );
      const currentY = Math.floor(
        (y + this.getItemHeight / 2) / this.getItemHeight
      );

      this.moveToIndex = Math.min(
        currentY * this.column + currentX,
        this.list.length - 1
      );

      if (
        this.oldIndex !== this.moveToIndex &&
        this.oldIndex !== -1 &&
        this.moveToIndex !== -1
      ) {
        const newList = this.deepCopy(this.cloneList);
        newList.splice(
          this.moveToIndex,
          0,
          ...newList.splice(this.activeIndex, 1)
        );

        this.list.forEach((item, index) => {
          if (index !== this.activeIndex) {
            const itemIndex = newList.findIndex(
              (val) => val[this.itemKey] === item[this.itemKey]
            );
            [item.x, item.y] = this.getPosition(itemIndex);
          }
        });
        this.oldIndex = this.moveToIndex;
      }
    },

    //获取当前的位置
    getPosition(index) {
      const x = (index % this.column) * this.getItemWidth;
      const y = Math.floor(index / this.column) * this.getItemHeight;
      return [x, y];
    },

    //拖拽结束
    handleDragEnd(e) {
      if (this.disabled) return;
      if (
        this.moveToIndex !== -1 &&
        this.activeIndex !== -1 &&
        this.moveToIndex !== this.activeIndex
      ) {
        this.cloneList.splice(
          this.moveToIndex,
          0,
          ...this.cloneList.splice(this.activeIndex, 1)
        );
      } else {
        this.$set(this.list[this.activeIndex], "x", this.tempDragInfo.x);
        this.$set(this.list[this.activeIndex], "y", this.tempDragInfo.y);
      }
      this.initList(this.cloneList);
      const endList = this.list.map((item) =>
        this.omit(item, ["x", "y", "key"])
      );
      this.$emit("input", endList);
      this.$emit("end", endList);

      this.activeIndex = -1;
      this.oldIndex = -1;
      this.moveToIndex = -1;
      this.disabled = true;
    },

    deepCopy(source) {
      return JSON.parse(JSON.stringify(source));
    },

    /**
     * 排除掉obj里面的key值
     * @param {object} obj
     * @param {Array|string} args
     * @returns {object}
     */
    omit(obj, args) {
      if (!args) return obj;
      const newObj = {};
      const isString = typeof args === "string";
      const keys = Object.keys(obj).filter((item) => {
        if (isString) {
          return item !== args;
        }
        return !args.includes(item);
      });

      keys.forEach((key) => {
        if (obj[key] !== undefined) newObj[key] = obj[key];
      });
      return newObj;
    },

    handleSwitch(item, index) {
      this.$emit("switch", item, index);
    },
  },
  watch: {
    value: {
      handler() {
        this.initList(this.value);
      },
      immediate: true,
      deep: true,
    },
  },
};
</script>

<style lang="scss" scoped>
.base-drag-wrapper {
  opacity: 1;
  z-index: 1;
  &.active {
    opacity: 0.7;
    transform: scale(1.3);
    z-index: 99;
  }
}

.drag-item {
  font-size: 12px;

  .image-wrapper {
    border-radius: 20rpx;
    border: 2px solid transparent;
    overflow: hidden;
    box-sizing: border-box;
    image {
      display: block;
    }
  }

  .name {
    margin-top: 3px;
    color: #999;
    padding: 2px 5px;
    border-radius: 5px;
  }

  &.active {
    .image-wrapper {
      border: 2px solid $u-primary;
    }
    .name {
      background-color: $u-primary;
      color: #fff;
    }
  }
}
</style>
